#include <common.h>
#include <vmm.h>
#include <thread.h>
#include <process.h>

unsigned int *page_directory = (unsigned int *)PAGE_DIR_VIRTUAL_ADDR;
unsigned int *page_tables = (unsigned int *)PAGE_TABLE_VIRTUAL_ADDR;

void page_fault(registers *r);
void map(unsigned int virtual, unsigned int physical, unsigned int flags);
void unmap(unsigned int virtual);

void switch_page_directory(page_directory_t *pd)
{
	current_directory = pd;
	asm volatile ("mov %0, %%cr3" : : "r" (pd));
}

char get_mapping(unsigned int virtual, unsigned int *physical)
{
	unsigned int virtual_page = virtual / 0x1000;
	unsigned int pt_index = PAGE_DIR_IDX(virtual_page);
	if(page_directory[pt_index] == 0)
		return 0;
	
	if(page_tables[virtual_page] != 0)
	{
		if(physical)*physical = page_tables[virtual_page] & PAGE_MASK;
		return 1;
	}
}
char get_mapping_ext(unsigned int virtual, unsigned int *physical, unsigned int *pd)
{
	unsigned int virtual_page = virtual / 0x1000;
	unsigned int pt_index = PAGE_DIR_IDX(virtual_page);
	unsigned int *page_d = (unsigned int *)TMP_SRC;
	unsigned int *page_t = (unsigned int *)TMP_DEST;
	map((unsigned int)TMP_SRC, (unsigned int)pd, PAGE_PRESENT | PAGE_WRITE);
	if(page_d[pt_index] == 0)
		return 0;
	map((unsigned int)TMP_DEST, (unsigned int)page_d[pt_index], PAGE_PRESENT | PAGE_WRITE);
	
	if(page_t[virtual_page] != 0)
	{
		if(physical)*physical = page_t[virtual_page] & PAGE_MASK;
		unmap(TMP_DEST);
		unmap(TMP_SRC);
		return 1;
	}
	unmap(TMP_DEST);
	unmap(TMP_SRC);
}
unsigned int copy_page2(unsigned int src)
{

	unsigned int page = (unsigned int)pmm_alloc_page();
	map(TMP_PAGE_S,src,PAGE_PRESENT);
	map(TMP_PAGE_D,page, PAGE_PRESENT | PAGE_WRITE);

	memcopy(TMP_PAGE_D,TMP_PAGE_S, 0x1000);
	
	unmap(TMP_PAGE_D);
	unmap(TMP_PAGE_S);
	return page;
}

page_directory_t copy_table(page_directory_t src)
{
	map((unsigned int)TMP_TABLE_S, (unsigned int)src, PAGE_PRESENT);
	unsigned int *src_page = (unsigned int *)TMP_TABLE_S;
	
	unsigned int newtable = (unsigned int)pmm_alloc_page();
	map((unsigned int)TMP_TABLE_D, (unsigned int)newtable, PAGE_PRESENT | PAGE_WRITE );
	unsigned int *pt = (unsigned int *)TMP_TABLE_D;
	
	memset(pt, 0, 0x100);
	
	int i;
	for(i=0; i< 1024; i++)
	{
		if(src_page[i] != 0)
		{
			pt[i]= copy_page2(src_page[i]&PAGE_MASK);
			pt[i] |= src_page[i]&(~PAGE_MASK);
		}
	}
	unmap(TMP_TABLE_S);
	unmap(TMP_TABLE_D);
	return newtable;
}

page_directory_t *copy_directory(page_directory_t *src_dir)
{

	map((unsigned int)TMP_SRC, (unsigned int)src_dir, PAGE_PRESENT | PAGE_WRITE);
	page_directory_t *src = (page_directory_t *)TMP_SRC;
	
	unsigned int newdir = (unsigned int)pmm_alloc_page();
	map((unsigned int)TMP_DEST, newdir, PAGE_PRESENT | PAGE_WRITE );
	page_directory_t *dir = (page_directory_t *)TMP_DEST;
	
	memset(dir, 0, 0x1000);
	
	dir[1023] = (unsigned int)newdir | PAGE_PRESENT | PAGE_WRITE;
	
	int i;
	for(i = 0; i< 1023; i++)
	{

		if(src[i] != 0)
		{
			if(!src[i])
				continue;
			if(src[i] == kernel_directory[i])
			{
				dir[i] = kernel_directory[i];
			} else {
				dir[i] = copy_table(src[i] & PAGE_MASK) | src[i]&(~PAGE_MASK);
			}
		}
	}

	unmap(TMP_DEST);
	unmap(TMP_SRC);
	

	return (page_directory_t *)newdir;
	
}

void empty_page(unsigned int page)
{
	map(TMP_DEST, page, PAGE_PRESENT | PAGE_WRITE);
	memset(TMP_DEST,0,0x1000);
	unmap(TMP_DEST);
}

void init_vmm()
{
	int i;
	unsigned int cr0;
	register_int_handler(14, &page_fault);

	page_directory_t *pd = (page_directory_t *)pmm_alloc_page();
	memset(pd,0,0x1000);

	pd[0x300] = pmm_alloc_page() | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;

	unsigned int *pt = (unsigned int *)(pd[0x300] & PAGE_MASK);
	for(i=0; i < 1024; i++)
	{
		pt[i] = i*0x1000 | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;
	}
	
	pd[1023] = (unsigned int)pd | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;
	current_directory = pd;
	asm volatile ("mov %0, %%cr3" : : "r" (pd));
	
	asm volatile ("mov %%cr0, %0" : "=r" (cr0));
	cr0 |= 0x80000000;
	asm volatile ("mov %0, %%cr0" : : "r" (cr0));	

	unsigned int pt_idx = PAGE_DIR_IDX((PMM_STACK_ADDRESS>>12));
	page_directory[pt_idx] = ((unsigned int)pmm_alloc_page() | PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
	memset(&page_tables[pt_idx*1024],0,0x1000);	


	fill_memorymap();
	pmm_paging_active = 1;
//	kernel_directory = (page_directory_t *)((unsigned int)pd + 0xC0000000);
	kernel_directory = (page_directory_t *)KERNEL_DIRECTORY;
	map(KERNEL_DIRECTORY, (unsigned int)pd, PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
//	map(0xD0001000,pmm_alloc_page(),PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
}

void map(unsigned int virtual, unsigned int physical, unsigned int flags)
{
	unsigned int virtual_page = virtual/0x1000;
	unsigned int pt_index = PAGE_DIR_IDX(virtual_page);
	
	if(page_directory[pt_index] == 0)
	{

		page_directory[pt_index] = pmm_alloc_page() | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;
		if(virtual >= 0xC0000000)
		{
			kernel_directory[pt_index] == page_directory[pt_index];
		}
		memset(&page_tables[pt_index*1024], 0, 0x1000);
	}
	page_tables[virtual_page] = (physical & PAGE_MASK) | flags;
	
}

void unmap(unsigned int virtual)
{
	unsigned int virtual_page = virtual/0x1000;
	page_tables[virtual_page] = 0;
	asm volatile ("invlpg (%0)" : : "a" (virtual));
}

void freemap(unsigned int virtual)
{
	unsigned int ph;
	get_mapping(virtual, &ph);
	unmap(virtual);
	pmm_free_page(ph);
}

void page_fault(registers *r)
{
	unsigned int cr2;
	asm volatile("cli");
	asm volatile ("mov %%cr2, %0" : "=r" (cr2));
	//unsigned int cr22 = (cr2 &~ 0xFFF);
	unsigned int virtual_page = (cr2)/0x1000;
	unsigned int pt_index = PAGE_DIR_IDX(virtual_page);

//	printf("Kernel_dir%x",kernel_directory[pt_index]);

	if((cr2 >= 0xC0000000) && (kernel_directory[pt_index] != 0) && ((r->err_no & 0x1)== 0))
	{
		printf("Is in kernel directory %x", page_directory[pt_index]);
		printf(" %x", kernel_directory[pt_index]);
		page_directory[pt_index] = kernel_directory[pt_index];
		asm volatile ("invlpg (%0)" : : "a" (cr2));
	//	return;
	}
	
	if(panic_lookup(r->eip))
		printf("\n\nPage fault at:[%x] - %s", r->eip, panic_lookup(r->eip));
	else
		printf("\n\nPage fault at:[%x] ", r->eip);
	printf("\n Process %x", current_thread->process->pid);
	printf("\nTried to access:%x", cr2);
	printf("\nError code:%x",r->err_no);
	if(r->err_no & 0x1) printf(" Page was present."); else printf(" Page was not present.");
	if(r->err_no & 0x2) printf(" Tried to write."); else printf(" Tried to read.");
	if(r->err_no & 0x4) printf(" Was in user-mode."); else printf(" Was in kernel mode.");
	if(r->err_no & 0x8) printf(" Reserved bits were overwritten.");
	if(r->err_no & 0x10) printf(" Faulted at instruction fetch.");
	
	printf("\n");
	
	printf(" ds:%x",r->ds);
	printf("\nedi:%x",r->edi);
	printf(" esi:%x",r->esi);
	printf(" ebp:%x",r->ebp);
	printf(" esp:%x",r->esp);
	printf(" ebx:%x",r->ebx);
	printf(" edx:%x",r->edx);
	printf(" ecx:%x",r->ecx);
	printf(" eax:%x",r->eax);
	printf("\nint_no:%x",r->int_no);
	printf(" err_code:%x",r->err_no);
	printf("\neip:%x",r->eip);
	printf(" cs:%x",r->cs);
	printf(" eflags:%x",r->eflags);
	printf(" useresp:%x",r->useresp);
	printf(" ss:%x",r->ss);
	
		
//	panic("Page fault");
	//print_stack_trace(r->ebp);

	for(;;);
}


